package com.example.project.projectspringcloud.config;

import com.example.project.projectspringcloud.common.Constants;
import com.example.project.projectspringcloud.dao.user.UserProfileMapper;
import com.example.project.projectspringcloud.entity.user.User;
import com.example.project.projectspringcloud.entity.user.UserProfile;
import com.example.project.projectspringcloud.service.impl.user.UserServiceImpl;
import com.example.project.projectspringcloud.service.user.UserService;
import com.example.project.projectspringcloud.utils.*;
import com.google.gson.Gson;
import com.qq.connect.QQConnectException;
import com.qq.connect.api.OpenID;
import com.qq.connect.api.qzone.UserInfo;
import com.qq.connect.javabeans.AccessToken;
import com.qq.connect.javabeans.qzone.UserInfoBean;
import com.qq.connect.oauth.Oauth;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.security.MD5Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.*;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import weibo4j.Users;
import weibo4j.model.WeiboException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Date;
import java.util.Map;

import static org.apache.http.HttpStatus.SC_FORBIDDEN;

/**
 * spring boot security configuration
 */
@EnableWebSecurity
public class WebSecurityConfig {

    private static final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

    /**
     * data source for JdbcRememberMeImpl
     */
    @Autowired
    @Qualifier("dataSource")
    private DataSource dataSource;

    @Autowired
    private SecurityCustomValidateCode securityCustomValidateCode;

    @Bean
    UserDetailsService customUserService() {
        return new UserServiceImpl();
    }

    /**
     * 返回 RememberMeServices 实例
     *
     * @return the remember me services
     */
    @Bean
    public JdbcTokenRepositoryImpl rememberMeRepository() {
        JdbcTokenRepositoryImpl rememberMeTokenRepository = new JdbcTokenRepositoryImpl();
        // 此处需要设置数据源，否则无法从数据库查询验证信息
        rememberMeTokenRepository.setDataSource(dataSource);

        /*// 此处的key可以为任意非空值(null 或 ""),单必须和起前面, rememberMeServices(RememberMeServices rememberMeServices).key(key)的值相同
        PersistentTokenBasedRememberMeServices rememberMeServices =
                new PersistentTokenBasedRememberMeServices("INTERNAL_SECRET_KEY", customUserService(), rememberMeTokenRepository);

        // 该参数不是必须的，默认值为 "remember-me", 但如果设置必须和页面复选框的 name 一致
        rememberMeServices.setParameter("remember-me");*/
        return rememberMeTokenRepository;
    }

    /**
     * 管理员登录拦截
     */
    @Configuration
    @Order(1)
    public class WebSecurityAdmin extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(customUserService()).passwordEncoder(new PasswordEncoder() {

                @Override
                public String encode(CharSequence rawPassword) {
                    return MD5Encoder.encode(((String) rawPassword).getBytes());
                }

                @Override
                public boolean matches(CharSequence rawPassword, String encodedPassword) {
                    return StringUtils.isNotBlank(encodedPassword) && ObjectUtil.isNotNull(rawPassword)
                            && StringUtil.md5encode(rawPassword.toString()).toLowerCase().equals(encodedPassword.toLowerCase());
                }
            });
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // add verification validate code before spring-security authfilter
            http.addFilterBefore(securityCustomValidateCode, UsernamePasswordAuthenticationFilter.class);

            // cancel iframe cros  // .csrf().disable()
//            Spring Boot不允许加载iframe问题解决
            http.headers().frameOptions().disable();

            http.formLogin().loginPage("/admin/login").defaultSuccessUrl("/admin").failureUrl("/admin/login?error=true").permitAll();
            http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout")).logoutSuccessUrl("/admin").invalidateHttpSession(true);
            //配置角色访问得url
            http.antMatcher("/admfffin/**").authorizeRequests().anyRequest().hasAnyRole("ADMIN", "EDITOR")
                    // 启用 remember me 自动登录
                    .and().rememberMe().tokenRepository(rememberMeRepository()).tokenValiditySeconds(604800).key(Constants.contextPath)
                    .and().exceptionHandling().accessDeniedHandler(
                    new AccessDeniedHandler() {
                        @Override
                        public void handle(HttpServletRequest httpServletRequest,
                                           HttpServletResponse httpServletResponse,
                                           AccessDeniedException e)
                                throws IOException, ServletException {
                            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                            if (auth != null) {
                                new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, auth);
                            }
                            logger.error("spring security access denied !", e);
                            httpServletResponse.sendError(SC_FORBIDDEN);
                        }
                    }
            );
        }

    }

    /**
     * 用户登录拦截
     */
    @Configuration
    @Order(2)
    public class WebSecurityUser extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(customUserService()).passwordEncoder(new PasswordEncoder() {

                @Override
                public String encode(CharSequence rawPassword) {
                    return MD5Encoder.encode(((String) rawPassword).getBytes());
                }

                @Override
                public boolean matches(CharSequence rawPassword, String encodedPassword) {
                    if (StringUtils.isNotBlank(encodedPassword)) {
                        return StringUtil.md5encode(rawPassword.toString()).toLowerCase().equals(encodedPassword.toLowerCase());
                    }
                    return false;
                }
            });
        }

        /**
         * 自定义登录失败
         */
        private class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

            // AuthenticationException 认证过程中产生的异常
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                                AuthenticationException exception) throws IOException, ServletException {
                super.setDefaultFailureUrl("/login?error=true");
                request.getSession().setAttribute("SPRING_SECURITY_LAST_USERNAME", request.getParameter("username"));
                super.onAuthenticationFailure(request, response, exception);
            }
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // add verification validate code before spring-security authfilter
            // http.addFilterBefore(securityCustomValidateCode, UsernamePasswordAuthenticationFilter.class);

            // 短信验证码登录拦截
            http.addFilterAt(smsLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

            // 在 UsernamePasswordAuthenticationFilter 前添加 AuthenticationFilter
            http.addFilterAt(qqAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
            http.addFilterAt(sinaWeiboAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
            http.addFilterAt(wechatAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

            // cancel iframe cros
            http.headers().frameOptions().disable();
            http.csrf().ignoringAntMatchers("/pay/**");

            http.formLogin().loginPage("/login").permitAll()
                    .successHandler(new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                            Authentication authentication) throws IOException, ServletException {
                            // 登录成功, 判断用户信息是否完全
                            logger.info("++++\n++++\n++++\n++++\n++++\n++++\n++++\n++++\n++++\n++++\n");
                            User user = WebUtil.getCurrentUser();
                            if (StringUtils.isBlank(user.getWechat())) {
                                response.sendRedirect("/personal?type=1");
                            }
                        }
                    }).defaultSuccessUrl("/").failureUrl("/login?error=true").failureHandler(new MyAuthenctiationFailureHandler());
            http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/").invalidateHttpSession(true);

            http.authorizeRequests().antMatchers("/order/**", "/api/collect", "/api/like", "/personal*", "/personal/**").hasRole("USER")
                    .antMatchers("/pay/**").permitAll()
                    // 启用 remember me
                    .and().rememberMe().tokenRepository(rememberMeRepository()).tokenValiditySeconds(604800).key(Constants.contextPath)
                    .and().exceptionHandling().accessDeniedHandler(
                    new AccessDeniedHandler() {
                        @Override
                        public void handle(HttpServletRequest httpServletRequest,
                                           HttpServletResponse httpServletResponse,
                                           AccessDeniedException e)
                                throws IOException, ServletException {
                            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                            if (auth != null) {
                                new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, auth);
                            }
                            logger.error("spring security access denied!", e);
                            logger.error("url: {}", httpServletRequest.getRequestURL());
                            logger.error("param: {}", httpServletRequest.getAttributeNames());
                            httpServletResponse.sendError(SC_FORBIDDEN);
                        }
                    }
            );
        }

        private final Logger tplogger = LoggerFactory.getLogger(WebSecurityUser.class);

        @Autowired
        private UserProfileMapper userProfileMapper;

        @Autowired
        private UserService userService;

        @Autowired
        private RedisUtil redisUtil;

        @Autowired
        private MessageSource messageSource;

        /**
         * 短信登录拦截器
         *
         * @return
         */
        private AbstractAuthenticationProcessingFilter smsLoginAuthenticationFilter() {
            AbstractAuthenticationProcessingFilter ap = new AbstractAuthenticationProcessingFilter(new AntPathRequestMatcher("/login-sms", "POST")) {

                @Override
                public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
                    // 获取短信验证码
                    String sms = request.getParameter("sms");
                    String username = request.getParameter("username");
                    if (StringUtils.isNotBlank(sms) && sms.equals(redisUtil.get(Constants.SMS_VALIDATE_CODE_OF + username))) {
                        redisUtil.del(Constants.SMS_VALIDATE_CODE_OF + username);
                        User entity = new User();
                        entity.setMobile(username);
                        entity = userService.getWithPrivate(entity);
                        UserDetails userDetails = customUserService().loadUserByUsername(entity.getAccount());
                        return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getAuthorities(), userDetails.getAuthorities());
                    } else {
                        throw new AuthenticationServiceException(StringUtil.translateI18N(messageSource, Constants.Tip.SMS_VALIDATE_CODE_DIFFERENT));
                    }
                }
            };

            SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
            successHandler.setAlwaysUseDefaultTargetUrl(true);
            successHandler.setDefaultTargetUrl("/personal");
            ap.setAuthenticationSuccessHandler(successHandler);

            SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
            failureHandler.setDefaultFailureUrl("/login-sms");
            ap.setAuthenticationFailureHandler(failureHandler);

            return ap;
        }

        /**
         * 公用三方登录方法
         *
         * @param existUserProfile
         * @param currentUser
         * @param thirdUserInfo
         * @param openID
         * @param request
         * @param response
         * @return
         * @throws IOException
         */
        private Authentication doProcess(UserProfile existUserProfile, User currentUser, Object thirdUserInfo, String openID,
                                         HttpServletRequest request, HttpServletResponse response) throws IOException {
            // 第三方账号已绑定用户ID
            if (existUserProfile.getUserId() != null) {
                // 直接登录
                User existUser = userService.getWithPrivate(new User(existUserProfile.getUserId()));
                tplogger.info("ID 为 {} 用户已在本站完成注册!", existUserProfile.getUserId());

                // response.sendRedirect("/index");
                UserDetails userDetails = customUserService().loadUserByUsername(existUser.getAccount());
                return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getAuthorities(), userDetails.getAuthorities());
            }
            // 第三方账号未绑定本地用户
            else {
                // 当前用户未登录, 跳转注册页面
                if (currentUser == null) {
                    request.getSession().setAttribute("thirdpart-user-type", existUserProfile.getType());
                    request.getSession().setAttribute("thirdpart-user", thirdUserInfo);
                    request.getSession().setAttribute("openID", openID);

                    response.sendRedirect("/register");
                }
                // 当前用户已登录
                else {
                    // 绑定登录用户第三方信息
                    existUserProfile.setUserId(currentUser.getId());
                    userProfileMapper.updateByPrimaryKeySelective(existUserProfile);
                    response.sendRedirect("/personal/safe");
                }
                return null;
            }
        }

        /**
         * 自定义 QQ登录 过滤器
         */
        private AbstractAuthenticationProcessingFilter qqAuthenticationFilter() {
            // QQAuthenticationFilter authenticationFilter = new QQAuthenticationFilter("/qq");
            // QQ登录后返回请求, 获取QQ用户信息
            AbstractAuthenticationProcessingFilter ap = new AbstractAuthenticationProcessingFilter("/qq") {
                @Override
                public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
                    AccessToken accessToken = null;
                    try {
                        accessToken = (new Oauth()).getAccessTokenByRequest(request);

                        if (accessToken != null && StringUtils.isNotBlank(accessToken.getAccessToken())) {
                            // 利用获取到的accessToken 去获取当前用的openid -------- start
                            OpenID openIDObj = new OpenID(accessToken.getAccessToken());
                            String openID = openIDObj.getUserOpenID();

                            tplogger.info("Welcome，QQ代号为 {} 的用户!", openID);

                            if (StringUtils.isNotBlank(openID)) {
                                // 获取用户第三方信息
                                UserInfo userInfo = new UserInfo(accessToken.getAccessToken(), openID);
                                UserInfoBean userInfoBean = userInfo.getUserInfo();
                                // 根据第三方信息查找本地数据
                                UserProfile entity = new UserProfile();
                                entity.setOpenId(openID);
                                UserProfile exist = userProfileMapper.selectOne(entity);
                                // 如果存在第三方账号

//                                弄了一个三方表.....
                                if (exist == null) {
                                    exist = new UserProfile();
                                    exist.setName(userInfoBean.getNickname());
                                    exist.setOpenId(openID);
                                    exist.setType(Constants.ThirdPartType.QQ.toString());
                                    exist.setDate(new Date());
                                    exist.setAvatar(userInfoBean.getAvatar().getAvatarURL100());
                                    userProfileMapper.insert(exist);
                                }

                                return doProcess(exist, WebUtil.getCurrentUser(), userInfoBean, openID, request, response);
                            }
                        }
                    } catch (QQConnectException e) {
                        tplogger.error("Third part QQ login: get accessToken failed!");
                        return null;
                    }
                    return null;
                }
            };

            SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
            successHandler.setAlwaysUseDefaultTargetUrl(true);
            successHandler.setDefaultTargetUrl("/index");
            /*ap.setAuthenticationManager(new AuthenticationManager() {

                @Override
                public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                    UserProfile entity = new UserProfile();
                    entity.setValue((String) authentication.getCredentials());
                    UserProfile exist = userProfileMapper.selectBySelective(entity);
                    // 如果第三方账号已绑定
                    if (exist != null && StringUtils.isNotBlank(exist.getName())) {
                        Integer userId = entity.getUserId();
                        tplogger.info("ID 为 {} 用户已在本站完成注册!", userId);
                        successHandler.setDefaultTargetUrl("/index");
                    }
                    // 如果未绑定, 跳转到第三方账号注册页面
                    else {
                        successHandler.setDefaultTargetUrl("/register");
                    }

                    // 权限列表
                    List<GrantedAuthority> grantedAuthorities = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
                    try {
                        UserInfo userInfo = new UserInfo(authentication.getName(), (String) authentication.getCredentials());
                        UserInfoBean userInfoBean = userInfo.getUserInfo();
                        return new UsernamePasswordAuthenticationToken(new User(userInfoBean.getNickname(), "qq", grantedAuthorities), grantedAuthorities);
                    } catch (QQConnectException e) {
                        throw new BadCredentialsException("Bad Credentials");
                    }
                }
            });*/
            ap.setAuthenticationSuccessHandler(successHandler);
            return ap;
        }

        /**
         * 自定义 Sina Weibo登录 过滤器
         */
        private AbstractAuthenticationProcessingFilter sinaWeiboAuthenticationFilter() {
            AbstractAuthenticationProcessingFilter ap = new AbstractAuthenticationProcessingFilter("/sinaweibo") {
                @Override
                public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
                    weibo4j.http.AccessToken accessToken = null;
                    try {
                        weibo4j.Oauth oauth = new weibo4j.Oauth();
                        String code = request.getParameter("code");
                        accessToken = oauth.getAccessTokenByCode(code);

                        if (accessToken != null && StringUtils.isNotBlank(accessToken.getAccessToken())) {
                            // 利用获取到的accessToken 去获取Weibo User
                            Users weiboUsers = new Users();
                            weiboUsers.setToken(accessToken.getAccessToken());
                            weibo4j.model.User weiboUser = weiboUsers.showUserById(accessToken.getUid());

                            tplogger.info("Welcome，新浪微博代号为 {} 的用户!", weiboUser.getId());

                            UserProfile entity = new UserProfile();
                            entity.setOpenId(String.valueOf(weiboUser.getId()));
                            UserProfile exist = userProfileMapper.selectOne(entity);

                            // 如果不存在第三方信息
                            if (exist == null) {
                                // 添加用户第三方信息
                                exist = new UserProfile();
                                exist.setName(weiboUser.getName());
                                exist.setOpenId(weiboUser.getId().toString());
                                exist.setType(Constants.ThirdPartType.SINAWEIBO.toString());
                                exist.setDate(new Date());
                                exist.setAvatar(weiboUser.getAvatarLarge());
                                userProfileMapper.insert(exist);
                            }
                            return doProcess(exist, WebUtil.getCurrentUser(), weiboUser, exist.getOpenId(), request, response);
                        }
                    } catch (WeiboException e) {
                        tplogger.error("Third part Sina Weibo login: get accessToken failed!");
                        return null;
                    }
                    return null;
                }
            };

            SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
            successHandler.setAlwaysUseDefaultTargetUrl(true);
            successHandler.setDefaultTargetUrl("/index");
            ap.setAuthenticationSuccessHandler(successHandler);
            return ap;
        }

        /**
         * 自定义 WeChat 登录 过滤器
         */
        private AbstractAuthenticationProcessingFilter wechatAuthenticationFilter() {
            AbstractAuthenticationProcessingFilter ap = new AbstractAuthenticationProcessingFilter("/wechat") {
                @Override
                public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
                    // 如果是登录用户绑定, 获取登录用户ID
                    String currentUserId = request.getParameter("userId");

                    String code = request.getParameter("code");
                    String accessToken = HttpUtil.get(String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
                            "wxf6a149f7343f3ac0",
                            "54ca20214159997e5b48b6d298cf3643",
                            code));
                    Map accessTokenMap = new Gson().fromJson(accessToken, Map.class);
                    if (accessTokenMap.get("errcode") != null) {
                        return null;
                    }
                    accessToken = (String) accessTokenMap.get("access_token");
                    String openId = (String) accessTokenMap.get("openid");

                    String userInfo = HttpUtil.get(String.format("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", accessToken, openId));
                    Map userInfoMap = new Gson().fromJson(userInfo, Map.class);
                    try {
                        if (StringUtils.isNotBlank(accessToken)) {
                            tplogger.info("Welcome，微信代号为 {} 的用户!", (String) userInfoMap.get("unionid"));

                            // 获取第三方信息
                            UserProfile entity = new UserProfile();
                            entity.setValueTwo((String) userInfoMap.get("unionid"));
                            UserProfile exist = userProfileMapper.selectOne(entity);

                            // 如果第三方信息存在
                            if (exist == null) {
                                exist = new UserProfile();
                                exist.setName((String) userInfoMap.get("nickname"));
                                exist.setOpenId((String) userInfoMap.get("openid"));
                                exist.setType(Constants.ThirdPartType.WECHAT.toString());
                                exist.setDate(new Date());
                                exist.setAvatar((String) userInfoMap.get("headimgurl"));
                                exist.setValueTwo((String) userInfoMap.get("unionid"));
                                userProfileMapper.insert(exist);
                            }
                            return doProcess(exist, WebUtil.getCurrentUser(), userInfoMap, exist.getOpenId(), request, response);
                        }
                    } catch (Exception e) {
                        tplogger.error("Third part WeChat login: get accessToken failed!");
                        return null;
                    }

                    return null;
                }
            };

            SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
            successHandler.setAlwaysUseDefaultTargetUrl(true);
            successHandler.setDefaultTargetUrl("/index");
            ap.setAuthenticationSuccessHandler(successHandler);
            return ap;
        }
    }

    @Configuration
    @Order
    public class WebSecurityOther extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // add verification validate code before spring-security authfilter
            // http.addFilterBefore(securityCustomValidateCode, UsernamePasswordAuthenticationFilter.class);

            // cancel iframe cros
            http.headers().frameOptions().disable();

            http.authorizeRequests().antMatchers("/**").permitAll();
        }
    }
}
